library(rental)
library(cancensus)
library(tidyverse)
library(sf)

Listings Overview

As an example we read the August data for unfurnished listings for Vancouver, Calgary and Toronto.

Reading regions list from local cache.
Cached regions list may be out of date. Set `use_cache = FALSE` to update it.Reading geo data from local cache.
[1] "Toronto (C)"
[1] "Calgary (CY)"
[1] "Vancouver (CY)"

Vancouver over time

regions=list_census_regions('CA16', use_cache = TRUE) %>% filter(level=="CSD",name == "Vancouver")
Reading regions list from local cache.
Cached regions list may be out of date. Set `use_cache = FALSE` to update it.
geo=get_census(dataset = 'CA16',regions=as_census_region_list(regions),geo_format='sf',level="Regions")
Reading geo data from local cache.
start_time="2017-05-01"
end_time="2017-12-16"
  ls <- get_listings(start_time,end_time,geo$geometry,filter = 'unfurnished')
  summary=ls %>% as.data.frame %>% select("price","beds") %>% group_by(beds) %>% summarize(median=paste0("$",format(median(price),big.mark=",")), count=n()) %>% mutate(name="Vancouver")
  
ls<-st_as_sf(ls)
base=getOption("custom_data_path")
nbhds=st_read(paste0(base,"local_area_boundary_shp/local_area_boundary.shp")) %>% st_transform(4326)
Reading layer `local_area_boundary' from data source `/Users/jens/.custom_data/local_area_boundary_shp/local_area_boundary.shp' using driver `ESRI Shapefile'
Simple feature collection with 22 features and 2 fields
geometry type:  POLYGON
dimension:      XY
bbox:           xmin: 483644.8 ymin: 5449580 xmax: 498313 ymax: 5460349
epsg (SRID):    26910
proj4string:    +proj=utm +zone=10 +datum=NAD83 +units=m +no_defs
downtown=st_read(paste0(base,"downtown_yvr.geojson"))
Reading layer `OGRGeoJSON' from data source `/Users/jens/.custom_data/downtown_yvr.geojson' using driver `GeoJSON'
Simple feature collection with 1 feature and 0 fields
geometry type:  POLYGON
dimension:      XY
bbox:           xmin: -123.1684 ymin: 49.26915 xmax: -123.1004 ymax: 49.31595
epsg (SRID):    4326
proj4string:    +proj=longlat +datum=WGS84 +no_defs
yaletown=st_read(paste0(base,"yaletown.geojson"))
Reading layer `OGRGeoJSON' from data source `/Users/jens/.custom_data/yaletown.geojson' using driver `GeoJSON'
Simple feature collection with 1 feature and 0 fields
geometry type:  POLYGON
dimension:      XY
bbox:           xmin: -123.1274 ymin: 49.27282 xmax: -123.1148 ymax: 49.27791
epsg (SRID):    4326
proj4string:    +proj=longlat +datum=WGS84 +no_defs
westend=st_read(paste0(base,"westend.geojson"))
Reading layer `OGRGeoJSON' from data source `/Users/jens/.custom_data/westend.geojson' using driver `GeoJSON'
Simple feature collection with 1 feature and 0 fields
geometry type:  POLYGON
dimension:      XY
bbox:           xmin: -123.1491 ymin: 49.27626 xmax: -123.1229 ymax: 49.29423
epsg (SRID):    4326
proj4string:    +proj=longlat +datum=WGS84 +no_defs
ls$downtown=as.logical(st_intersects(ls,downtown))
although coordinates are longitude/latitude, st_intersects assumes that they are planar
ls$westend=as.logical(st_intersects(ls,westend))
although coordinates are longitude/latitude, st_intersects assumes that they are planar
ls$yaletown=as.logical(st_intersects(ls,yaletown))
although coordinates are longitude/latitude, st_intersects assumes that they are planar
ls$nbhd_dtn=as.logical(st_intersects(ls,nbhds %>% filter(NAME=="Downtown")))
although coordinates are longitude/latitude, st_intersects assumes that they are planar
ls <- ls %>% mutate(
  nbhd_dtn=ifelse(is.na(nhd_dtn),FALSE,nhd_dtn),
  downtown=ifelse(is.na(downtown),FALSE,downtown),
  westend=ifelse(is.na(westend),FALSE,westend),
  yaletown=ifelse(is.na(yaletown),FALSE,yaletown)
  )
ls <- st_join(ls,nbhds)
although coordinates are longitude/latitude, st_intersects assumes that they are planar
ggplot(nbhds, aes(fill=NAME)) +geom_sf()

area="Renfrew-Collingwood"
plot_data <- ls %>% filter(NAME==area,as.integer(beds)<3, price<5000) %>% mutate(beds=ifelse(as.integer(beds)>=5,"5+",beds))
  ggplot(plot_data, aes(x=post_date, y=price, color=beds,group=beds)) + 
  geom_point(size=0.01,alpha=0.2) +
  geom_smooth(method = "loess", se = FALSE) +
    scale_color_discrete(name="Bedrooms") +
    theme_bw() +
    labs(title=paste0(area," Listings (unfurnished) ",format(nrow(plot_data))),x="Date",y="Asking Rent")

NA
plot_data <- ls %>% filter(as.integer(beds)<3, downtown==TRUE, price<5000) %>% mutate(ppsf=price/size) %>% filter(!is.na(ppsf) & ppsf<7.5)
  ggplot(plot_data, aes(x=post_date, y=ppsf, color=beds,group=beds)) + 
  geom_point(size=0.01,alpha=0.2) +
  geom_smooth(method = "loess", se = FALSE) +
    theme_bw()

plot_data <- ls %>% filter(as.integer(beds)<=2) %>% mutate(beds=ifelse(as.integer(beds)>=5,"5+",beds),
                           type=paste0(beds, ifelse(downtown," downtown","")))
# totals <- plot_data %>%
#   group_by(downtown,beds) %>% summarize(price,sub)
  ggplot(plot_data, aes(x=post_date, y=price, color=type,group=type)) + 
  geom_point(size=0.01,alpha=0.2) +
    scale_color_brewer(palette = "Paired") +
  geom_smooth(method = "loess", se = FALSE) +
    theme_bw()

Map

library(sf)
library(ggplot2)
geo=get_census(dataset = 'CA16',regions=list(CMA="59933"),geo_format='sf',level="Regions")
ls <- get_listings(start_time,end_time,geo$geometry,beds=c(1),filter = 'unfurnished')
  summary=ls %>% as.data.frame %>% select("price","beds") %>% group_by(beds) %>% summarize(median=paste0("$",format(median(price),big.mark=","))) %>% mutate(name=row$name)
cts=get_census(dataset = 'CA16',regions=list(CMA="59933"),geo_format='sf',level="CSD")
min_listings=10
median_rent <- function(v){
  result <- ifelse(length(v)>min_listings, median(v),NA)
  return(result)
}
aggregate_listings <- aggregate(cts %>% select("GeoUID"),ls,function(x){x})
data <- aggregate(ls %>% select("price"),cts,median_rent)
cutoffs=as.integer(quantile(data$price, probs=seq(0,1,0.1), na.rm=TRUE))
labels=factor(as.character(seq(1,length(cutoffs)-1) %>% lapply(function(i){return(paste0(cutoffs[i]," - ",cutoffs[i+1]))})),order=TRUE)
colors=setNames(RColorBrewer::brewer.pal(length(labels),"RdYlBu"),labels)
data$discrete_price= data$price %>% cut(breaks=cutoffs, labels=labels)
ggplot() +
  geom_sf(data=cts, fill="#808080", size=0.1) +
  geom_sf(data=data, aes(fill = discrete_price), size=0.1) +
  scale_fill_brewer(palette='RdYlBu', direction=-1, na.value="#808080",name="Median Price") +
  labs(title="August Studio and 1 Bedroom Unfurnished Median Ask") +
  theme_opts

Rent distributions by municipality

Looking into Coquitlam

region_name="Coquitlam" #"Richmond"
regions=as_census_region_list(search_census_regions(region_name,'CA16','CSD') %>% filter(name==region_name))
geo=get_census(dataset = 'CA16',regions=regions,geo_format='sf',level="Regions")
ls <- get_listings(start_time,end_time,geo$geometry,beds=c(1),filter = 'unfurnished',sanity=c(400,4000))
summary=ls %>% as.data.frame %>% 
  select("price","beds") %>% 
  group_by(beds) %>%
  summarize(median=paste0("$",format(median(price),big.mark=","))) %>% 
  mutate(name=row$name)
cutoffs=c(400,1350,4000)
labels=factor(as.character(seq(1,length(cutoffs)-1) %>% lapply(function(i){return(paste0(cutoffs[i]," - ",cutoffs[i+1]))})),order=TRUE)
colors=setNames(c("turquoise","purple"),labels)
ls$discrete_price= ls$price %>% cut(breaks=cutoffs, labels=labels)
#ls <- cbind(ls,st_coordinates(st_transform(ls,102002)$location))
ls <- cbind(ls,st_coordinates(ls$location))
library(ggmap)
base <- get_map(paste0(region_name,", Canada"), zoom=12, source = "stamen", maptype = "toner", 
    crop = T)
#ggplot() +
  ggmap(base) +
  #geom_sf(data=geo, fill="#808080", size=0.1) +
  #coord_sf(crs=st_crs(102002)) +
  geom_point(data=ls , aes(color = discrete_price, x=X, y=Y), shape=21, size=2) +
  scale_fill_manual(palette=colors) +
  labs(title="August Studio and 1 Bedroom Unfurnished Median Ask",color="Price") +
  theme_opts

Rent distributions over time

region_name="Vancouver" 
regions=as_census_region_list(search_census_regions(region_name,'CA16','CSD') %>% filter(name==region_name))
geo=get_census(dataset = 'CA16',regions=regions,geo_format='sf',level="Regions")
ls <- get_listings(start_time,end_time,geo$geometry,beds=c(1),filter = 'unfurnished',sanity=c(400,4000))
ls$year_month <- factor(substr(ls$post_date,0,7),ordered = TRUE)
#ls$year_month_day <- factor(substr(ls$post_date,0,10),ordered = TRUE)
 
#ls %>% group_by(year_month) %>% summarize(count=length(year_month)) %>% as.data.frame %>% select("year_month","count")
#ls %>% group_by(year_month_day) %>% summarize(count=length(year_month_day)) %>% as.data.frame %>% select("year_month_day","count")
  
 
plot_data <- ls %>% as.data.frame %>% select("price","year_month")
title="Distribution of Unfurnished 1br Rents, City of Vancouver"
p1 <- ggplot(plot_data) + 
  geom_density(aes(x=price, color=year_month)) +
  labs(title=title, color="Year-Month")
p2 <- ggplot(plot_data, aes(year_month, price))+ 
  geom_violin(aes(fill=year_month )) + 
  #geom_beeswarm(pch = 1, col='white', cex=0.8, alpha=0.6) +
  labs(title=title, fill="Year-Month", x="Year-Month")
grid.arrange(p1, p2, ncol=1)

Rent distributions by municipality

library(ggbeeswarm)
library(gridExtra)
region_names=c("Vancouver","Toronto","Victoria","Calgary")
regions= as_census_region_list(do.call(rbind,lapply(region_names,function(region_name){return((search_census_regions(region_name,'CA16','CSD') %>% filter(name==region_name)))})))
geo=get_census(dataset = 'CA16',regions=regions,geo_format='sf',level="Regions")
geometry=st_union(geo$geometry)
beds=2
ls <- get_listings(start_time,end_time,geometry,beds=c(beds),filter = 'unfurnished',sanity=c(400,5000))
  summary=ls %>% as.data.frame %>% select("price","beds") %>% group_by(beds) %>% summarize(median=paste0("$",format(median(price),big.mark=","))) %>% mutate(name=row$name)
  
geos=get_census(dataset = 'CA16',regions=regions,geo_format='sf',level="CSD") %>%
  st_join(ls) 
plot_data <- geos %>% as.data.frame %>% select("name","price") %>%
  rename(Municipality=name)
title=paste0("Distribution of Unfurnished ",beds,"br Rents, August 2017")
p1 <- ggplot(plot_data) + 
  geom_density(aes(x=price, color=Municipality)) +
  labs(title=title)
p2 <- ggplot(plot_data, aes(Municipality, price))+ 
  geom_violin(aes(fill=Municipality )) + 
  #geom_beeswarm(pch = 1, col='white', cex=0.8, alpha=0.6) +
  labs(title=title)
grid.arrange(p1, p2, ncol=1)

Checking Specific area

#geo=sf::read_sf("../data/custom_region.geojson")
geo=sf::read_sf("../data/victoria_stainsbury.geojson")
ls <- get_listings("2017-06-01","2017-09-01",geo$geometry,filter = 'unfurnished')
  summary=ls %>% as.data.frame %>% select("price","beds") %>% group_by(beds) %>% summarize(median=paste0("$",format(median(price),big.mark=","))) %>% mutate(name=row$name)
  ggplot(ls, aes(beds, price))+ 
  geom_violin(aes(fill=beds )) + 
  #geom_beeswarm(pch = 1, col='white', cex=0.8, alpha=0.6) +
  labs(title="June-August 1br unfurnished Custom Region")

NA
cutoffs=c(400,1050,4000)
labels=factor(as.character(seq(1,length(cutoffs)-1) %>% lapply(function(i){return(paste0(cutoffs[i]," - ",cutoffs[i+1]))})),order=TRUE)
colors=setNames(c("turquoise","purple"),labels)
ls$discrete_price= ls$price %>% cut(breaks=cutoffs, labels=labels)
ls <- cbind(ls,st_coordinates(ls$location))
base <- get_map(location=c(-122.84594535827637, 49.18422801616818), zoom=15, source = "stamen", maptype = "toner", 
    crop = T)
Map from URL : http://maps.googleapis.com/maps/api/staticmap?center=49.184228,-122.845945&zoom=15&size=640x640&scale=2&maptype=terrain&sensor=false
Map from URL : http://tile.stamen.com/toner/15/5201/11226.png
Map from URL : http://tile.stamen.com/toner/15/5202/11226.png
Map from URL : http://tile.stamen.com/toner/15/5203/11226.png
Map from URL : http://tile.stamen.com/toner/15/5201/11227.png
Map from URL : http://tile.stamen.com/toner/15/5202/11227.png
Map from URL : http://tile.stamen.com/toner/15/5203/11227.png
Map from URL : http://tile.stamen.com/toner/15/5201/11228.png
Map from URL : http://tile.stamen.com/toner/15/5202/11228.png
Map from URL : http://tile.stamen.com/toner/15/5203/11228.png
#ggplot() +
  ggmap(base) +
  #geom_sf(data=geo, fill="#808080", size=0.1) +
  #coord_sf(crs=st_crs(102002)) +
  geom_point(data=ls , aes(color = discrete_price, x=X, y=Y), shape=21, size=4) +
  scale_fill_manual(palette=colors) +
  geom_polygon(data= fortify(as(geo,"Spatial")), aes(x=long, y=lat), fill=NA, size=0.5,color='blue') +
  labs(title="August 1 Bedroom Unfurnished Median Ask",color="Price") +
  theme_opts
Regions defined for each Polygons

my_theme <- list(
  theme_minimal(),
  theme(axis.text = element_blank(), 
        axis.title = element_blank(), 
        axis.ticks = element_blank(),
        panel.grid.major = element_line(colour = "white"), 
        panel.grid.minor = element_line(colour = "white"),
        panel.background = element_blank(), 
        axis.line = element_blank())
)

Vancouver rent map

Reading regions list from local cache.
Reading geo data from local cache.
the condition has length > 1 and only the first element will be usedalthough coordinates are longitude/latitude, st_intersects assumes that they are planar

LS0tCnRpdGxlOiAiUmVudGFsIExpc3RpbmdzIERlbW8iCmF1dGhvcjogIkplbnMgdm9uIEJlcmdtYW5uIgpkYXRlOiAiYHIgU3lzLkRhdGUoKWAiCm91dHB1dDoKICBodG1sX2RvY3VtZW50OiBkZWZhdWx0CiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdAp2aWduZXR0ZTogPgogICVcVmlnbmV0dGVJbmRleEVudHJ5e1JlbnRhbCBMaXN0aW5ncyBEZW1vfQogICVcVmlnbmV0dGVFbmdpbmV7a25pdHI6OnJtYXJrZG93bn0KICAlXFZpZ25ldHRlRW5jb2Rpbmd7VVRGLTh9Ci0tLQoKYGBge3Igc2V0dXAsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkocmVudGFsKQpsaWJyYXJ5KGNhbmNlbnN1cykKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoc2YpCmBgYAoKCiMjIExpc3RpbmdzIE92ZXJ2aWV3CkFzIGFuIGV4YW1wbGUgd2UgcmVhZCB0aGUgQXVndXN0IGRhdGEgZm9yIHVuZnVybmlzaGVkIGxpc3RpbmdzIGZvciBWYW5jb3V2ZXIsIENhbGdhcnkgYW5kIFRvcm9udG8uCmBgYHtyLCBlY2hvPUZBTFNFfQoKcmVnaW9uX25hbWVzPWMoIlZhbmNvdXZlciIsIkNhbGdhcnkiLCJUb3JvbnRvIikKcmVnaW9ucz1saXN0X2NlbnN1c19yZWdpb25zKCdDQTE2JywgdXNlX2NhY2hlID0gVFJVRSkgJT4lIGZpbHRlcihsZXZlbD09IkNTRCIsbmFtZSAlaW4lIHJlZ2lvbl9uYW1lcykKZ2VvPWdldF9jZW5zdXMoZGF0YXNldCA9ICdDQTE2JyxyZWdpb25zPWFzX2NlbnN1c19yZWdpb25fbGlzdChyZWdpb25zKSxnZW9fZm9ybWF0PSdzZicsbGV2ZWw9IlJlZ2lvbnMiKQoKc3RhcnRfdGltZT0iMjAxNy0xMS0wMSIKZW5kX3RpbWU9IjIwMTgtMDEtMTYiCgpmb3IgKGkgaW4gMTpucm93KGdlbykpIHsKICByb3c9Z2VvW2ksXQogIGxzIDwtIGdldF9saXN0aW5ncyhzdGFydF90aW1lLGVuZF90aW1lLHJvdyRnZW9tZXRyeSxmaWx0ZXIgPSAndW5mdXJuaXNoZWQnKQogIHN1bW1hcnk9bHMgJT4lIGFzLmRhdGEuZnJhbWUgJT4lIHNlbGVjdCgicHJpY2UiLCJiZWRzIiwic2l6ZSIpICU+JSBncm91cF9ieShiZWRzKSAlPiUgc3VtbWFyaXplKG1lZGlhbj1wYXN0ZTAoIiQiLGZvcm1hdChtZWRpYW4ocHJpY2UpLGJpZy5tYXJrPSIsIikpLG1lZGlhbl9wcHNmPXBhc3RlMCgiJCIscm91bmQobWVkaWFuKHByaWNlL3NpemUsIG5hLnJtPVRSVUUpLDIpLCIvc2YiKSwgY291bnQ9bigpKSAlPiUgbXV0YXRlKG5hbWU9cm93JG5hbWUpCiAgcHJpbnQocm93JG5hbWUpCiAgcHJpbnQoc3VtbWFyeSkKICAjcHJpbnQocGFzdGUwKHJvdyRuYW1lLCIgbWVkaWFuIHByaWNlOiAkIixmb3JtYXQobWVkaWFuKGxzJHByaWNlKSxiaWcubWFyaz0iLCIpKSkKfQoKI2xzICU+JSBmaWx0ZXIoYmVkcz09IjEiKQoKYGBgCgoKIyMgVmFuY291dmVyIG92ZXIgdGltZQpgYGB7cn0KcmVnaW9ucz1saXN0X2NlbnN1c19yZWdpb25zKCdDQTE2JywgdXNlX2NhY2hlID0gVFJVRSkgJT4lIGZpbHRlcihsZXZlbD09IkNTRCIsbmFtZSA9PSAiVmFuY291dmVyIikKZ2VvPWdldF9jZW5zdXMoZGF0YXNldCA9ICdDQTE2JyxyZWdpb25zPWFzX2NlbnN1c19yZWdpb25fbGlzdChyZWdpb25zKSxnZW9fZm9ybWF0PSdzZicsbGV2ZWw9IlJlZ2lvbnMiKQoKc3RhcnRfdGltZT0iMjAxNy0wNS0wMSIKZW5kX3RpbWU9IjIwMTctMTItMTYiCgoKICBscyA8LSBnZXRfbGlzdGluZ3Moc3RhcnRfdGltZSxlbmRfdGltZSxnZW8kZ2VvbWV0cnksZmlsdGVyID0gJ3VuZnVybmlzaGVkJykKICBzdW1tYXJ5PWxzICU+JSBhcy5kYXRhLmZyYW1lICU+JSBzZWxlY3QoInByaWNlIiwiYmVkcyIpICU+JSBncm91cF9ieShiZWRzKSAlPiUgc3VtbWFyaXplKG1lZGlhbj1wYXN0ZTAoIiQiLGZvcm1hdChtZWRpYW4ocHJpY2UpLGJpZy5tYXJrPSIsIikpLCBjb3VudD1uKCkpICU+JSBtdXRhdGUobmFtZT0iVmFuY291dmVyIikKICAKYGBgCgpgYGB7cn0KbHM8LXN0X2FzX3NmKGxzKQpiYXNlPWdldE9wdGlvbigiY3VzdG9tX2RhdGFfcGF0aCIpCm5iaGRzPXN0X3JlYWQocGFzdGUwKGJhc2UsImxvY2FsX2FyZWFfYm91bmRhcnlfc2hwL2xvY2FsX2FyZWFfYm91bmRhcnkuc2hwIikpICU+JSBzdF90cmFuc2Zvcm0oNDMyNikKZG93bnRvd249c3RfcmVhZChwYXN0ZTAoYmFzZSwiZG93bnRvd25feXZyLmdlb2pzb24iKSkKeWFsZXRvd249c3RfcmVhZChwYXN0ZTAoYmFzZSwieWFsZXRvd24uZ2VvanNvbiIpKQp3ZXN0ZW5kPXN0X3JlYWQocGFzdGUwKGJhc2UsIndlc3RlbmQuZ2VvanNvbiIpKQpscyRkb3dudG93bj1hcy5sb2dpY2FsKHN0X2ludGVyc2VjdHMobHMsZG93bnRvd24pKQpscyR3ZXN0ZW5kPWFzLmxvZ2ljYWwoc3RfaW50ZXJzZWN0cyhscyx3ZXN0ZW5kKSkKbHMkeWFsZXRvd249YXMubG9naWNhbChzdF9pbnRlcnNlY3RzKGxzLHlhbGV0b3duKSkKbHMkbmJoZF9kdG49YXMubG9naWNhbChzdF9pbnRlcnNlY3RzKGxzLG5iaGRzICU+JSBmaWx0ZXIoTkFNRT09IkRvd250b3duIikpKQpscyA8LSBscyAlPiUgbXV0YXRlKAogIG5iaGRfZHRuPWlmZWxzZShpcy5uYShuaGRfZHRuKSxGQUxTRSxuaGRfZHRuKSwKICBkb3dudG93bj1pZmVsc2UoaXMubmEoZG93bnRvd24pLEZBTFNFLGRvd250b3duKSwKICB3ZXN0ZW5kPWlmZWxzZShpcy5uYSh3ZXN0ZW5kKSxGQUxTRSx3ZXN0ZW5kKSwKICB5YWxldG93bj1pZmVsc2UoaXMubmEoeWFsZXRvd24pLEZBTFNFLHlhbGV0b3duKQogICkKCgpscyA8LSBzdF9qb2luKGxzLG5iaGRzKQoKCmdncGxvdChuYmhkcywgYWVzKGZpbGw9TkFNRSkpICtnZW9tX3NmKCkKCgpgYGAKCmBgYHtyfQphcmVhPSJSZW5mcmV3LUNvbGxpbmd3b29kIgpwbG90X2RhdGEgPC0gbHMgJT4lIGZpbHRlcihOQU1FPT1hcmVhLGFzLmludGVnZXIoYmVkcyk8MywgcHJpY2U8NTAwMCkgJT4lIG11dGF0ZShiZWRzPWlmZWxzZShhcy5pbnRlZ2VyKGJlZHMpPj01LCI1KyIsYmVkcykpCiAgZ2dwbG90KHBsb3RfZGF0YSwgYWVzKHg9cG9zdF9kYXRlLCB5PXByaWNlLCBjb2xvcj1iZWRzLGdyb3VwPWJlZHMpKSArIAogIGdlb21fcG9pbnQoc2l6ZT0wLjAxLGFscGhhPTAuMikgKwogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsb2VzcyIsIHNlID0gRkFMU0UpICsKICAgIHNjYWxlX2NvbG9yX2Rpc2NyZXRlKG5hbWU9IkJlZHJvb21zIikgKwogICAgdGhlbWVfYncoKSArCiAgICBsYWJzKHRpdGxlPXBhc3RlMChhcmVhLCIgTGlzdGluZ3MgKHVuZnVybmlzaGVkKSAiLGZvcm1hdChucm93KHBsb3RfZGF0YSkpKSx4PSJEYXRlIix5PSJBc2tpbmcgUmVudCIpCiAgCmBgYAoKCgpgYGB7cn0KcGxvdF9kYXRhIDwtIGxzICU+JSBmaWx0ZXIoYXMuaW50ZWdlcihiZWRzKTwzLCBkb3dudG93bj09VFJVRSwgcHJpY2U8NTAwMCkgJT4lIG11dGF0ZShwcHNmPXByaWNlL3NpemUpICU+JSBmaWx0ZXIoIWlzLm5hKHBwc2YpICYgcHBzZjw3LjUpCiAgZ2dwbG90KHBsb3RfZGF0YSwgYWVzKHg9cG9zdF9kYXRlLCB5PXBwc2YsIGNvbG9yPWJlZHMsZ3JvdXA9YmVkcykpICsgCiAgZ2VvbV9wb2ludChzaXplPTAuMDEsYWxwaGE9MC4yKSArCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxvZXNzIiwgc2UgPSBGQUxTRSkgKwogICAgdGhlbWVfYncoKQoKYGBgCgoKYGBge3J9CnBsb3RfZGF0YSA8LSBscyAlPiUgZmlsdGVyKGFzLmludGVnZXIoYmVkcyk8PTIpICU+JSBtdXRhdGUoYmVkcz1pZmVsc2UoYXMuaW50ZWdlcihiZWRzKT49NSwiNSsiLGJlZHMpLAogICAgICAgICAgICAgICAgICAgICAgICAgICB0eXBlPXBhc3RlMChiZWRzLCBpZmVsc2UoZG93bnRvd24sIiBkb3dudG93biIsIiIpKSkKIyB0b3RhbHMgPC0gcGxvdF9kYXRhICU+JQojICAgZ3JvdXBfYnkoZG93bnRvd24sYmVkcykgJT4lIHN1bW1hcml6ZShwcmljZSxzdWIpCiAgZ2dwbG90KHBsb3RfZGF0YSwgYWVzKHg9cG9zdF9kYXRlLCB5PXByaWNlLCBjb2xvcj10eXBlLGdyb3VwPXR5cGUpKSArIAogIGdlb21fcG9pbnQoc2l6ZT0wLjAxLGFscGhhPTAuMikgKwogICAgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGUgPSAiUGFpcmVkIikgKwogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsb2VzcyIsIHNlID0gRkFMU0UpICsKICAgIHRoZW1lX2J3KCkKYGBgCgoKCiMjIE1hcAoKYGBge3IsIGluY2x1ZGU9RkFMU0V9CmJnX2NvbG9yPSIjYzBjMGMwIgp0aGVtZV9vcHRzPC1saXN0KHRoZW1lKHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgICAgI3BhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2JsYW5rKCksICNidWcsIG5vdCB3b3JraW5nCiAgICAgICAgICAgICAgICAgICAgICAgcGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfbGluZShjb2xvdXIgPSBiZ19jb2xvciksCiAgICAgICAgICAgICAgICAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gYmdfY29sb3IsIGNvbG91ciA9IE5BKSwKICAgICAgICAgICAgICAgICAgICAgICBwbG90LmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbD1iZ19jb2xvciwgc2l6ZT0xLGxpbmV0eXBlPSJzb2xpZCIpLAogICAgICAgICAgICAgICAgICAgICAgIGF4aXMubGluZSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgICAgICBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCkpKQpgYGAKCgoKCmBgYHtyIHByaWNlX21hcCwgZmlnLmhlaWdodD01LCBmaWcud2lkdGg9NSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbGlicmFyeShzZikKbGlicmFyeShnZ3Bsb3QyKQoKZ2VvPWdldF9jZW5zdXMoZGF0YXNldCA9ICdDQTE2JyxyZWdpb25zPWxpc3QoQ01BPSI1OTkzMyIpLGdlb19mb3JtYXQ9J3NmJyxsZXZlbD0iUmVnaW9ucyIpCgpscyA8LSBnZXRfbGlzdGluZ3Moc3RhcnRfdGltZSxlbmRfdGltZSxnZW8kZ2VvbWV0cnksYmVkcz1jKDEpLGZpbHRlciA9ICd1bmZ1cm5pc2hlZCcpCiAgc3VtbWFyeT1scyAlPiUgYXMuZGF0YS5mcmFtZSAlPiUgc2VsZWN0KCJwcmljZSIsImJlZHMiKSAlPiUgZ3JvdXBfYnkoYmVkcykgJT4lIHN1bW1hcml6ZShtZWRpYW49cGFzdGUwKCIkIixmb3JtYXQobWVkaWFuKHByaWNlKSxiaWcubWFyaz0iLCIpKSkgJT4lIG11dGF0ZShuYW1lPXJvdyRuYW1lKQoKY3RzPWdldF9jZW5zdXMoZGF0YXNldCA9ICdDQTE2JyxyZWdpb25zPWxpc3QoQ01BPSI1OTkzMyIpLGdlb19mb3JtYXQ9J3NmJyxsZXZlbD0iQ1NEIikKCm1pbl9saXN0aW5ncz0xMAoKbWVkaWFuX3JlbnQgPC0gZnVuY3Rpb24odil7CiAgcmVzdWx0IDwtIGlmZWxzZShsZW5ndGgodik+bWluX2xpc3RpbmdzLCBtZWRpYW4odiksTkEpCiAgcmV0dXJuKHJlc3VsdCkKfQoKYWdncmVnYXRlX2xpc3RpbmdzIDwtIGFnZ3JlZ2F0ZShjdHMgJT4lIHNlbGVjdCgiR2VvVUlEIiksbHMsZnVuY3Rpb24oeCl7eH0pCgpkYXRhIDwtIGFnZ3JlZ2F0ZShscyAlPiUgc2VsZWN0KCJwcmljZSIpLGN0cyxtZWRpYW5fcmVudCkKCgpjdXRvZmZzPWFzLmludGVnZXIocXVhbnRpbGUoZGF0YSRwcmljZSwgcHJvYnM9c2VxKDAsMSwwLjEpLCBuYS5ybT1UUlVFKSkKbGFiZWxzPWZhY3Rvcihhcy5jaGFyYWN0ZXIoc2VxKDEsbGVuZ3RoKGN1dG9mZnMpLTEpICU+JSBsYXBwbHkoZnVuY3Rpb24oaSl7cmV0dXJuKHBhc3RlMChjdXRvZmZzW2ldLCIgLSAiLGN1dG9mZnNbaSsxXSkpfSkpLG9yZGVyPVRSVUUpCmNvbG9ycz1zZXROYW1lcyhSQ29sb3JCcmV3ZXI6OmJyZXdlci5wYWwobGVuZ3RoKGxhYmVscyksIlJkWWxCdSIpLGxhYmVscykKZGF0YSRkaXNjcmV0ZV9wcmljZT0gZGF0YSRwcmljZSAlPiUgY3V0KGJyZWFrcz1jdXRvZmZzLCBsYWJlbHM9bGFiZWxzKQoKCmdncGxvdCgpICsKICBnZW9tX3NmKGRhdGE9Y3RzLCBmaWxsPSIjODA4MDgwIiwgc2l6ZT0wLjEpICsKICBnZW9tX3NmKGRhdGE9ZGF0YSwgYWVzKGZpbGwgPSBkaXNjcmV0ZV9wcmljZSksIHNpemU9MC4xKSArCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZT0nUmRZbEJ1JywgZGlyZWN0aW9uPS0xLCBuYS52YWx1ZT0iIzgwODA4MCIsbmFtZT0iTWVkaWFuIFByaWNlIikgKwogIGxhYnModGl0bGU9IkF1Z3VzdCBTdHVkaW8gYW5kIDEgQmVkcm9vbSBVbmZ1cm5pc2hlZCBNZWRpYW4gQXNrIikgKwogIHRoZW1lX29wdHMKYGBgCgoKCiMjIFJlbnQgZGlzdHJpYnV0aW9ucyBieSBtdW5pY2lwYWxpdHkKCmBgYHtyLCBlY2hvPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KGdnYmVlc3dhcm0pCmxpYnJhcnkoZ3JpZEV4dHJhKQoKcmVnaW9ucz1hc19jZW5zdXNfcmVnaW9uX2xpc3Qoc2VhcmNoX2NlbnN1c19yZWdpb25zKCJWYW5jb3V2ZXIiLCdDQTE2JywnQ01BJykpCgpnZW89Z2V0X2NlbnN1cyhkYXRhc2V0ID0gJ0NBMTYnLHJlZ2lvbnM9cmVnaW9ucyxnZW9fZm9ybWF0PSdzZicsbGV2ZWw9IlJlZ2lvbnMiKQoKbHMgPC0gZ2V0X2xpc3RpbmdzKHN0YXJ0X3RpbWUsZW5kX3RpbWUsZ2VvJGdlb21ldHJ5LGJlZHM9YygxKSxmaWx0ZXIgPSAndW5mdXJuaXNoZWQnLHNhbml0eT1jKDQwMCw0MDAwKSkKICBzdW1tYXJ5PWxzICU+JSBhcy5kYXRhLmZyYW1lICU+JSBzZWxlY3QoInByaWNlIiwiYmVkcyIpICU+JSBncm91cF9ieShiZWRzKSAlPiUgc3VtbWFyaXplKG1lZGlhbj1wYXN0ZTAoIiQiLGZvcm1hdChtZWRpYW4ocHJpY2UpLGJpZy5tYXJrPSIsIikpKSAlPiUgbXV0YXRlKG5hbWU9cm93JG5hbWUpCgogIApnZW9zPWdldF9jZW5zdXMoZGF0YXNldCA9ICdDQTE2JyxyZWdpb25zPXJlZ2lvbnMsZ2VvX2Zvcm1hdD0nc2YnLGxldmVsPSJDU0QiKSAlPiUKICBzdF9qb2luKGxzKSAKCnRvcF9tdW5pcyA8LSBnZW9zICU+JSBncm91cF9ieShuYW1lKSAlPiUgc3VtbWFyaXplKGNvdW50PWxlbmd0aChuYW1lKSkgJT4lIAogIHRvcF9uKDUsY291bnQpICU+JSBwdWxsKCJuYW1lIikKCnBsb3RfZGF0YSA8LSBnZW9zICU+JSBmaWx0ZXIobmFtZSAlaW4lIHRvcF9tdW5pcykgJT4lCiAgcmVuYW1lKE11bmljaXBhbGl0eT1uYW1lKQp0aXRsZT0iRGlzdHJpYnV0aW9uIG9mIFVuZnVybmlzaGVkIDFiciBSZW50cywgU2VwdGVtYmVyIDIwMTciCnAxIDwtIGdncGxvdChwbG90X2RhdGEpICsgCiAgZ2VvbV9kZW5zaXR5KGFlcyh4PXByaWNlLCBjb2xvcj1NdW5pY2lwYWxpdHkpKSArCiAgbGFicyh0aXRsZT10aXRsZSkKcDIgPC0gZ2dwbG90KHBsb3RfZGF0YSwgYWVzKE11bmljaXBhbGl0eSwgcHJpY2UpKSsgCiAgZ2VvbV92aW9saW4oYWVzKGZpbGw9TXVuaWNpcGFsaXR5ICkpICsgCiAgI2dlb21fYmVlc3dhcm0ocGNoID0gMSwgY29sPSd3aGl0ZScsIGNleD0wLjgsIGFscGhhPTAuNikgKwogIGxhYnModGl0bGU9dGl0bGUpCmdyaWQuYXJyYW5nZShwMSwgcDIsIG5jb2w9MSkKYGBgCgoKCiMjIExvb2tpbmcgaW50byBDb3F1aXRsYW0KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnJlZ2lvbl9uYW1lPSJDb3F1aXRsYW0iICMiUmljaG1vbmQiCnJlZ2lvbnM9YXNfY2Vuc3VzX3JlZ2lvbl9saXN0KHNlYXJjaF9jZW5zdXNfcmVnaW9ucyhyZWdpb25fbmFtZSwnQ0ExNicsJ0NTRCcpICU+JSBmaWx0ZXIobmFtZT09cmVnaW9uX25hbWUpKQoKZ2VvPWdldF9jZW5zdXMoZGF0YXNldCA9ICdDQTE2JyxyZWdpb25zPXJlZ2lvbnMsZ2VvX2Zvcm1hdD0nc2YnLGxldmVsPSJSZWdpb25zIikKCmxzIDwtIGdldF9saXN0aW5ncyhzdGFydF90aW1lLGVuZF90aW1lLGdlbyRnZW9tZXRyeSxiZWRzPWMoMSksZmlsdGVyID0gJ3VuZnVybmlzaGVkJyxzYW5pdHk9Yyg0MDAsNDAwMCkpCnN1bW1hcnk9bHMgJT4lIGFzLmRhdGEuZnJhbWUgJT4lIAogIHNlbGVjdCgicHJpY2UiLCJiZWRzIikgJT4lIAogIGdyb3VwX2J5KGJlZHMpICU+JQogIHN1bW1hcml6ZShtZWRpYW49cGFzdGUwKCIkIixmb3JtYXQobWVkaWFuKHByaWNlKSxiaWcubWFyaz0iLCIpKSkgJT4lIAogIG11dGF0ZShuYW1lPXJvdyRuYW1lKQoKY3V0b2Zmcz1jKDQwMCwxMzUwLDQwMDApCmxhYmVscz1mYWN0b3IoYXMuY2hhcmFjdGVyKHNlcSgxLGxlbmd0aChjdXRvZmZzKS0xKSAlPiUgbGFwcGx5KGZ1bmN0aW9uKGkpe3JldHVybihwYXN0ZTAoY3V0b2Zmc1tpXSwiIC0gIixjdXRvZmZzW2krMV0pKX0pKSxvcmRlcj1UUlVFKQpjb2xvcnM9c2V0TmFtZXMoYygidHVycXVvaXNlIiwicHVycGxlIiksbGFiZWxzKQpscyRkaXNjcmV0ZV9wcmljZT0gbHMkcHJpY2UgJT4lIGN1dChicmVha3M9Y3V0b2ZmcywgbGFiZWxzPWxhYmVscykKCiNscyA8LSBjYmluZChscyxzdF9jb29yZGluYXRlcyhzdF90cmFuc2Zvcm0obHMsMTAyMDAyKSRsb2NhdGlvbikpCmxzIDwtIGNiaW5kKGxzLHN0X2Nvb3JkaW5hdGVzKGxzJGxvY2F0aW9uKSkKCmxpYnJhcnkoZ2dtYXApCgpgYGAKCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpiYXNlIDwtIGdldF9tYXAocGFzdGUwKHJlZ2lvbl9uYW1lLCIsIENhbmFkYSIpLCB6b29tPTEyLCBzb3VyY2UgPSAic3RhbWVuIiwgbWFwdHlwZSA9ICJ0b25lciIsIAogICAgY3JvcCA9IFQpCgojZ2dwbG90KCkgKwogIGdnbWFwKGJhc2UpICsKICAjZ2VvbV9zZihkYXRhPWdlbywgZmlsbD0iIzgwODA4MCIsIHNpemU9MC4xKSArCiAgI2Nvb3JkX3NmKGNycz1zdF9jcnMoMTAyMDAyKSkgKwogIGdlb21fcG9pbnQoZGF0YT1scyAsIGFlcyhjb2xvciA9IGRpc2NyZXRlX3ByaWNlLCB4PVgsIHk9WSksIHNoYXBlPTIxLCBzaXplPTIpICsKICBzY2FsZV9maWxsX21hbnVhbChwYWxldHRlPWNvbG9ycykgKwogIGxhYnModGl0bGU9IkF1Z3VzdCBTdHVkaW8gYW5kIDEgQmVkcm9vbSBVbmZ1cm5pc2hlZCBNZWRpYW4gQXNrIixjb2xvcj0iUHJpY2UiKSArCiAgdGhlbWVfb3B0cwpgYGAKCiMjIFJlbnQgZGlzdHJpYnV0aW9ucyBvdmVyIHRpbWUKCmBgYHtyLCBmaWcuaGVpZ2h0PTcsIGZpZy53aWR0aD03LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQoKcmVnaW9uX25hbWU9IlZhbmNvdXZlciIgCnJlZ2lvbnM9YXNfY2Vuc3VzX3JlZ2lvbl9saXN0KHNlYXJjaF9jZW5zdXNfcmVnaW9ucyhyZWdpb25fbmFtZSwnQ0ExNicsJ0NTRCcpICU+JSBmaWx0ZXIobmFtZT09cmVnaW9uX25hbWUpKQoKZ2VvPWdldF9jZW5zdXMoZGF0YXNldCA9ICdDQTE2JyxyZWdpb25zPXJlZ2lvbnMsZ2VvX2Zvcm1hdD0nc2YnLGxldmVsPSJSZWdpb25zIikKCmxzIDwtIGdldF9saXN0aW5ncyhzdGFydF90aW1lLGVuZF90aW1lLGdlbyRnZW9tZXRyeSxiZWRzPWMoMSksZmlsdGVyID0gJ3VuZnVybmlzaGVkJyxzYW5pdHk9Yyg0MDAsNDAwMCkpCgpscyR5ZWFyX21vbnRoIDwtIGZhY3RvcihzdWJzdHIobHMkcG9zdF9kYXRlLDAsNyksb3JkZXJlZCA9IFRSVUUpCiNscyR5ZWFyX21vbnRoX2RheSA8LSBmYWN0b3Ioc3Vic3RyKGxzJHBvc3RfZGF0ZSwwLDEwKSxvcmRlcmVkID0gVFJVRSkKIAojbHMgJT4lIGdyb3VwX2J5KHllYXJfbW9udGgpICU+JSBzdW1tYXJpemUoY291bnQ9bGVuZ3RoKHllYXJfbW9udGgpKSAlPiUgYXMuZGF0YS5mcmFtZSAlPiUgc2VsZWN0KCJ5ZWFyX21vbnRoIiwiY291bnQiKQojbHMgJT4lIGdyb3VwX2J5KHllYXJfbW9udGhfZGF5KSAlPiUgc3VtbWFyaXplKGNvdW50PWxlbmd0aCh5ZWFyX21vbnRoX2RheSkpICU+JSBhcy5kYXRhLmZyYW1lICU+JSBzZWxlY3QoInllYXJfbW9udGhfZGF5IiwiY291bnQiKQogIAogCnBsb3RfZGF0YSA8LSBscyAlPiUgYXMuZGF0YS5mcmFtZSAlPiUgc2VsZWN0KCJwcmljZSIsInllYXJfbW9udGgiKQp0aXRsZT0iRGlzdHJpYnV0aW9uIG9mIFVuZnVybmlzaGVkIDFiciBSZW50cywgQ2l0eSBvZiBWYW5jb3V2ZXIiCnAxIDwtIGdncGxvdChwbG90X2RhdGEpICsgCiAgZ2VvbV9kZW5zaXR5KGFlcyh4PXByaWNlLCBjb2xvcj15ZWFyX21vbnRoKSkgKwogIGxhYnModGl0bGU9dGl0bGUsIGNvbG9yPSJZZWFyLU1vbnRoIikKcDIgPC0gZ2dwbG90KHBsb3RfZGF0YSwgYWVzKHllYXJfbW9udGgsIHByaWNlKSkrIAogIGdlb21fdmlvbGluKGFlcyhmaWxsPXllYXJfbW9udGggKSkgKyAKICAjZ2VvbV9iZWVzd2FybShwY2ggPSAxLCBjb2w9J3doaXRlJywgY2V4PTAuOCwgYWxwaGE9MC42KSArCiAgbGFicyh0aXRsZT10aXRsZSwgZmlsbD0iWWVhci1Nb250aCIsIHg9IlllYXItTW9udGgiKQpncmlkLmFycmFuZ2UocDEsIHAyLCBuY29sPTEpCmBgYAoKCgojIyBSZW50IGRpc3RyaWJ1dGlvbnMgYnkgbXVuaWNpcGFsaXR5CgpgYGB7ciwgZmlnLmhlaWdodD03LCBmaWcud2lkdGg9NywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbGlicmFyeShnZ2JlZXN3YXJtKQpsaWJyYXJ5KGdyaWRFeHRyYSkKCnJlZ2lvbl9uYW1lcz1jKCJWYW5jb3V2ZXIiLCJUb3JvbnRvIiwiVmljdG9yaWEiLCJDYWxnYXJ5IikKcmVnaW9ucz0gYXNfY2Vuc3VzX3JlZ2lvbl9saXN0KGRvLmNhbGwocmJpbmQsbGFwcGx5KHJlZ2lvbl9uYW1lcyxmdW5jdGlvbihyZWdpb25fbmFtZSl7cmV0dXJuKChzZWFyY2hfY2Vuc3VzX3JlZ2lvbnMocmVnaW9uX25hbWUsJ0NBMTYnLCdDU0QnKSAlPiUgZmlsdGVyKG5hbWU9PXJlZ2lvbl9uYW1lKSkpfSkpKQoKZ2VvPWdldF9jZW5zdXMoZGF0YXNldCA9ICdDQTE2JyxyZWdpb25zPXJlZ2lvbnMsZ2VvX2Zvcm1hdD0nc2YnLGxldmVsPSJSZWdpb25zIikKCmdlb21ldHJ5PXN0X3VuaW9uKGdlbyRnZW9tZXRyeSkKCmJlZHM9MgpscyA8LSBnZXRfbGlzdGluZ3Moc3RhcnRfdGltZSxlbmRfdGltZSxnZW9tZXRyeSxiZWRzPWMoYmVkcyksZmlsdGVyID0gJ3VuZnVybmlzaGVkJyxzYW5pdHk9Yyg0MDAsNTAwMCkpCiAgc3VtbWFyeT1scyAlPiUgYXMuZGF0YS5mcmFtZSAlPiUgc2VsZWN0KCJwcmljZSIsImJlZHMiKSAlPiUgZ3JvdXBfYnkoYmVkcykgJT4lIHN1bW1hcml6ZShtZWRpYW49cGFzdGUwKCIkIixmb3JtYXQobWVkaWFuKHByaWNlKSxiaWcubWFyaz0iLCIpKSkgJT4lIG11dGF0ZShuYW1lPXJvdyRuYW1lKQoKICAKZ2Vvcz1nZXRfY2Vuc3VzKGRhdGFzZXQgPSAnQ0ExNicscmVnaW9ucz1yZWdpb25zLGdlb19mb3JtYXQ9J3NmJyxsZXZlbD0iQ1NEIikgJT4lCiAgc3Rfam9pbihscykgCgoKcGxvdF9kYXRhIDwtIGdlb3MgJT4lIGFzLmRhdGEuZnJhbWUgJT4lIHNlbGVjdCgibmFtZSIsInByaWNlIikgJT4lCiAgcmVuYW1lKE11bmljaXBhbGl0eT1uYW1lKQp0aXRsZT1wYXN0ZTAoIkRpc3RyaWJ1dGlvbiBvZiBVbmZ1cm5pc2hlZCAiLGJlZHMsImJyIFJlbnRzLCBBdWd1c3QgMjAxNyIpCnAxIDwtIGdncGxvdChwbG90X2RhdGEpICsgCiAgZ2VvbV9kZW5zaXR5KGFlcyh4PXByaWNlLCBjb2xvcj1NdW5pY2lwYWxpdHkpKSArCiAgbGFicyh0aXRsZT10aXRsZSkKcDIgPC0gZ2dwbG90KHBsb3RfZGF0YSwgYWVzKE11bmljaXBhbGl0eSwgcHJpY2UpKSsgCiAgZ2VvbV92aW9saW4oYWVzKGZpbGw9TXVuaWNpcGFsaXR5ICkpICsgCiAgI2dlb21fYmVlc3dhcm0ocGNoID0gMSwgY29sPSd3aGl0ZScsIGNleD0wLjgsIGFscGhhPTAuNikgKwogIGxhYnModGl0bGU9dGl0bGUpCmdyaWQuYXJyYW5nZShwMSwgcDIsIG5jb2w9MSkKYGBgCgoKIyMgQ2hlY2tpbmcgU3BlY2lmaWMgYXJlYQoKCmBgYHtyfQojZ2VvPXNmOjpyZWFkX3NmKCIuLi9kYXRhL2N1c3RvbV9yZWdpb24uZ2VvanNvbiIpCmdlbz1zZjo6cmVhZF9zZigiLi4vZGF0YS92aWN0b3JpYV9zdGFpbnNidXJ5Lmdlb2pzb24iKQoKCgpscyA8LSBnZXRfbGlzdGluZ3MoIjIwMTctMDYtMDEiLCIyMDE3LTA5LTAxIixnZW8kZ2VvbWV0cnksZmlsdGVyID0gJ3VuZnVybmlzaGVkJykKICBzdW1tYXJ5PWxzICU+JSBhcy5kYXRhLmZyYW1lICU+JSBzZWxlY3QoInByaWNlIiwiYmVkcyIpICU+JSBncm91cF9ieShiZWRzKSAlPiUgc3VtbWFyaXplKG1lZGlhbj1wYXN0ZTAoIiQiLGZvcm1hdChtZWRpYW4ocHJpY2UpLGJpZy5tYXJrPSIsIikpKSAlPiUgbXV0YXRlKG5hbWU9cm93JG5hbWUpCgogIGdncGxvdChscywgYWVzKGJlZHMsIHByaWNlKSkrIAogIGdlb21fdmlvbGluKGFlcyhmaWxsPWJlZHMgKSkgKyAKICAjZ2VvbV9iZWVzd2FybShwY2ggPSAxLCBjb2w9J3doaXRlJywgY2V4PTAuOCwgYWxwaGE9MC42KSArCiAgbGFicyh0aXRsZT0iSnVuZS1BdWd1c3QgMWJyIHVuZnVybmlzaGVkIEN1c3RvbSBSZWdpb24iKQogIApgYGAKCmBgYHtyLCBmaWcuaGVpZ2h0PTUsIGZpZy53aWR0aD01fQoKCmN1dG9mZnM9Yyg0MDAsMTA1MCw0MDAwKQpsYWJlbHM9ZmFjdG9yKGFzLmNoYXJhY3RlcihzZXEoMSxsZW5ndGgoY3V0b2ZmcyktMSkgJT4lIGxhcHBseShmdW5jdGlvbihpKXtyZXR1cm4ocGFzdGUwKGN1dG9mZnNbaV0sIiAtICIsY3V0b2Zmc1tpKzFdKSl9KSksb3JkZXI9VFJVRSkKY29sb3JzPXNldE5hbWVzKGMoInR1cnF1b2lzZSIsInB1cnBsZSIpLGxhYmVscykKbHMkZGlzY3JldGVfcHJpY2U9IGxzJHByaWNlICU+JSBjdXQoYnJlYWtzPWN1dG9mZnMsIGxhYmVscz1sYWJlbHMpCmxzIDwtIGNiaW5kKGxzLHN0X2Nvb3JkaW5hdGVzKGxzJGxvY2F0aW9uKSkKCmJhc2UgPC0gZ2V0X21hcChsb2NhdGlvbj1jKC0xMjIuODQ1OTQ1MzU4Mjc2MzcsIDQ5LjE4NDIyODAxNjE2ODE4KSwgem9vbT0xNSwgc291cmNlID0gInN0YW1lbiIsIG1hcHR5cGUgPSAidG9uZXIiLCAKICAgIGNyb3AgPSBUKQoKI2dncGxvdCgpICsKICBnZ21hcChiYXNlKSArCiAgI2dlb21fc2YoZGF0YT1nZW8sIGZpbGw9IiM4MDgwODAiLCBzaXplPTAuMSkgKwogICNjb29yZF9zZihjcnM9c3RfY3JzKDEwMjAwMikpICsKICBnZW9tX3BvaW50KGRhdGE9bHMgLCBhZXMoY29sb3IgPSBkaXNjcmV0ZV9wcmljZSwgeD1YLCB5PVkpLCBzaGFwZT0yMSwgc2l6ZT00KSArCiAgc2NhbGVfZmlsbF9tYW51YWwocGFsZXR0ZT1jb2xvcnMpICsKICBnZW9tX3BvbHlnb24oZGF0YT0gZm9ydGlmeShhcyhnZW8sIlNwYXRpYWwiKSksIGFlcyh4PWxvbmcsIHk9bGF0KSwgZmlsbD1OQSwgc2l6ZT0wLjUsY29sb3I9J2JsdWUnKSArCiAgbGFicyh0aXRsZT0iQXVndXN0IDEgQmVkcm9vbSBVbmZ1cm5pc2hlZCBNZWRpYW4gQXNrIixjb2xvcj0iUHJpY2UiKSArCiAgdGhlbWVfb3B0cwoKYGBgCgoKCmBgYHtyfQpteV90aGVtZSA8LSBsaXN0KAogIHRoZW1lX21pbmltYWwoKSwKICB0aGVtZShheGlzLnRleHQgPSBlbGVtZW50X2JsYW5rKCksIAogICAgICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X2JsYW5rKCksIAogICAgICAgIGF4aXMudGlja3MgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfbGluZShjb2xvdXIgPSAid2hpdGUiKSwgCiAgICAgICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfbGluZShjb2xvdXIgPSAid2hpdGUiKSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLCAKICAgICAgICBheGlzLmxpbmUgPSBlbGVtZW50X2JsYW5rKCkpCikKYGBgCgoKIyMgVmFuY291dmVyIHJlbnQgbWFwCmBgYHtyLCBlY2hvPUZBTFNFLCBtZXNzYWdlPVRSVUUsIHdhcm5pbmc9VFJVRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoY2FuY2Vuc3VzKQpsaWJyYXJ5KHJlbnRhbCkKbGlicmFyeShzZikKcmVnaW9ucz1saXN0X2NlbnN1c19yZWdpb25zKCdDQTE2JywgdXNlX2NhY2hlID0gVFJVRSkgJT4lIGZpbHRlcihsZXZlbD09IkNNQSIsbmFtZSA9PSAiVmFuY291dmVyIikKZ2VvPWdldF9jZW5zdXMoZGF0YXNldCA9ICdDQTE2JyxyZWdpb25zPWFzX2NlbnN1c19yZWdpb25fbGlzdChyZWdpb25zKSxnZW9fZm9ybWF0PSdzZicsbGV2ZWw9IkNUIikKCnN0YXJ0X3RpbWU9IjIwMTctMDktMDEiCmVuZF90aW1lPSIyMDE3LTEyLTA3IgoKcHBzZl9mb3JtYXR0ZXIgPC0gZnVuY3Rpb24oeCl7cmV0dXJuKHBhc3RlMCgiJCIscm91bmQoeCwyKSwiL3NmIikpfQoKbHMgPC0gZ2V0X2xpc3RpbmdzKHN0YXJ0X3RpbWUsZW5kX3RpbWUsc3RfdW5pb24oZ2VvJGdlb21ldHJ5KSxiZWRzPWMoJzAnLCcxJywnMicpLGZpbHRlciA9ICd1bmZ1cm5pc2hlZCcpICU+JQogIG11dGF0ZShwcHNmPXByaWNlL3NpemUpCiAgCmdlb19saXN0aW5ncyA8LSBzdF9qb2luKGdlbywgZmlsdGVyKGxzLCFpcy5uYShwcHNmKSkpICU+JSAKICBncm91cF9ieShHZW9VSUQpICU+JSAKICBzdW1tYXJpemUocHBzZj1tZWRpYW4ocHBzZiksY291bnQ9bigpKQoKZ2dwbG90KGdlb19saXN0aW5ncyAlPiUgIG11dGF0ZShwcHNmPWlmZWxzZShjb3VudD49NSxwcHNmLE5BKSkgJT4lIHN0X2FzX3NmKSArCiAgZ2VvbV9zZihhZXMoZmlsbD1wcHNmKSxzaXplPU5BKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2MobmFtZT0iQXNraW5nIFJlbnQvc2YiLCBvcHRpb249Im1hZ21hIikgKwogIGxhYnModGl0bGU9Ik9jdG9iZXIgMSB0aHJvdWdoIERlYyA3IEFza2luZyBSZW50L3NmIikgKwogIG15X3RoZW1lCgoKYGBgCg==